﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Cms.Domain
{
    [Serializable]
    public class Folder : IAuditable, IComparable<Folder>
    {
        private IList<AdministratorGroup> _allowedAdministratorGroups;
        private IList<Category> _categories;
        private IList<AdministratorGroup> _deniedAdministratorGroups;
        private IList<UserGroup> _deselectedUserGroups;
        private DateTime? _endPublishing;
        private int _id;
        private string _name;
        private IList<Page> _pages;
        private Folder _parent;
        private IList<UserGroup> _requiredUserGroups;
        private Site _site;
        private DateTime? _startPublishing;
        private IList<Folder> _subFolders;

        public Folder()
        {
            _subFolders = new List<Folder>();
            _pages = new List<Page>();
            _categories = new List<Category>();
            _allowedAdministratorGroups = new List<AdministratorGroup>();
            _deniedAdministratorGroups = new List<AdministratorGroup>();
            _requiredUserGroups = new List<UserGroup>();
            _deselectedUserGroups = new List<UserGroup>();
        }

        public virtual string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        
        public virtual Site Site
        {
            get { return _site; }
            set { _site = value; }
        }
        
        public virtual Folder Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }
        
        public virtual IList<Folder> SubFolders
        {
            get { return _subFolders; }
            set { _subFolders = value; }
        }

        public virtual IList<Page> Pages
        {
            get { return _pages; }
            set { _pages = value; }
        }
        
        public virtual DateTime? StartPublishing
        {
            get { return _startPublishing; }
            set { _startPublishing = value; }
        }

        public virtual DateTime? EndPublishing
        {
            get { return _endPublishing; }
            set { _endPublishing = value; }
        }
        
        public virtual IList<Category> Categories
        {
            get { return _categories; }
            set { _categories = value; }
        }

        public virtual IList<AdministratorGroup> AllowedAdministratorGroups
        {
            get { return _allowedAdministratorGroups; }
            set { _allowedAdministratorGroups = value; }
        }
        
        public virtual IList<AdministratorGroup> DeniedAdministratorGroups
        {
            get { return _deniedAdministratorGroups; }
            set { _deniedAdministratorGroups = value; }
        }

        public virtual IList<UserGroup> RequiredUserGroups
        {
            get { return _requiredUserGroups; }
            set { _requiredUserGroups = value; }
        }
        
        public virtual IList<UserGroup> DeselectedUserGroups
        {
            get { return _deselectedUserGroups; }
            set { _deselectedUserGroups = value; }
        }

        #region IAuditable Members

        public virtual int ID
        {
            get { return _id; }
            set { _id = value; }
        }

        public virtual string GetState()
        {
            return "Name = " + _name +
                   ", ParentID = " + (_parent != null ? _parent.ID.ToString() : "NULL") +
                   ", Path = " + GetVirtualPath();
        }

        #endregion

        public virtual bool IsParentTo(Folder folder)
        {
            if (folder == null)
                return false;
            Folder parent = folder.Parent;
            while (parent != null)
            {
                if (parent.ID == ID)
                    return true;
                parent = parent.Parent;
            }
            return false;
        }
        
        public virtual string GetVirtualPath()
        {
            IList<string> folders = new List<string>();
            folders.Add(Name);
            Folder p = _parent;
            while (p != null)
            {
                folders.Add(p.Name);
                p = p.Parent;
            }
            string[] folderArray = folders.ToArray();
            Array.Reverse(folderArray);
            return string.Join("/", folderArray);
        }
        
        public virtual bool IsInCategory(string name)
        {
            foreach (Category category in _categories)
                if (category.Name.ToLower() == name.ToLower())
                    return true;
            if (_parent != null)
                return _parent.IsInCategory(name);
            return false;
        }

        public virtual IList<Category> GetAllCategories()
        {
            IList<Category> categories = new List<Category>();
            foreach (Category c in _categories)
                categories.Add(c);
            if (_parent != null)
                foreach (Category c in _parent.GetAllCategories())
                    if (!categories.Contains(c))
                        categories.Add(c);
            return categories;
        }
        
        public virtual IList<AdministratorGroup> GetAllAllowedAdministratorGroups()
        {
            IList<AdministratorGroup> groups = new List<AdministratorGroup>();
            foreach (AdministratorGroup group in _allowedAdministratorGroups)
                groups.Add(group);
            if (_parent != null)
                foreach (AdministratorGroup group in _parent.GetAllAllowedAdministratorGroups())
                    if (!groups.Contains(group))
                        groups.Add(group);
            return groups;
        }
        
        public virtual IList<AdministratorGroup> GetAllDeniedAdministratorGroups()
        {
            IList<AdministratorGroup> groups = new List<AdministratorGroup>();
            foreach (AdministratorGroup group in _deniedAdministratorGroups)
                groups.Add(group);
            if (_parent != null)
                foreach (AdministratorGroup group in _parent.GetAllDeniedAdministratorGroups())
                    if (!groups.Contains(group))
                        groups.Add(group);
            return groups;
        }

        public virtual bool AllowsAdministrator(Administrator administrator)
        {
            bool subItemAllowsAdministrator = SubFolderOrPageExplicitlyAllowsAdministrator(this, administrator);
            if (this.GetAllDeniedAdministratorGroups().Contains(administrator.Group))
            {
                return subItemAllowsAdministrator;
            }
            if (this.GetAllAllowedAdministratorGroups().Contains(administrator.Group))
            {
                return true;
            }
            if (administrator.Group.DefaultPageAccess == DefaultAccess.Deny && subItemAllowsAdministrator)
                return true;
            return administrator.Group.DefaultPageAccess == DefaultAccess.Allow;
        }

        private bool SubFolderOrPageExplicitlyAllowsAdministrator(Folder folder, Administrator administrator)
        {
            bool allowed = false;
            foreach (Page page in folder.Pages)
            {
                if (page.AllowedAdministratorGroups.Contains(administrator.Group))
                {
                    allowed = true;
                    break;
                }
            }
            if (allowed)
                return true;
            foreach (Folder subFolder in folder.SubFolders)
            {
                if (
                    subFolder.AllowedAdministratorGroups.Contains(administrator.Group) || 
                    SubFolderOrPageExplicitlyAllowsAdministrator(subFolder, administrator))
                {
                    allowed = true;
                    break;
                }
            }
            return allowed;
        }

        public virtual IList<UserGroup> GetRequiredUserGroups()
        {
            IList<UserGroup> groups = new List<UserGroup>();
            foreach (UserGroup group in _requiredUserGroups)
                groups.Add(group);
            if (_parent != null)
                foreach (UserGroup group in _parent.GetRequiredUserGroups())
                    if (!groups.Contains(group))
                        groups.Add(group);
            foreach (UserGroup group in _deselectedUserGroups)
                if (groups.Contains(group))
                    groups.Remove(group);
            return groups;
        }

        public virtual void RemoveDeselectedUserGroups(IList<UserGroup> removedGroups)
        {
            foreach (Page page in _pages)
                foreach (UserGroup group in removedGroups)
                    if (page.DeselectedUserGroups.Contains(group))
                        page.DeselectedUserGroups.Remove(group);
            foreach (UserGroup group in removedGroups)
                if (_deselectedUserGroups.Contains(group))
                    _deselectedUserGroups.Remove(group);
            foreach (Folder subfolder in _subFolders)
                subfolder.RemoveDeselectedUserGroups(removedGroups);
        }

        public virtual IList<Page> GetAllPagesInThisFolderAndBelow()
        {
            IList<Page> pages = _pages.ToList();
            foreach (Page p in _subFolders.SelectMany(folder => folder.GetAllPagesInThisFolderAndBelow()))
            {
                pages.Add(p);
            }
            return pages;
        }

        public virtual int CompareTo(Folder other)
        {
            return _id > 0 && _id == other.ID ? 0 : 1;
        }
    }
}